]> dgit.raspbian.org Git - gnome-shell.git/commitdiff
quickSettings: Introduce QuickSettingsMenu
authorFlorian Müllner <fmuellner@gnome.org>
Sun, 24 Jul 2022 17:29:50 +0000 (19:29 +0200)
committerMarge Bot <marge-bot@gnome.org>
Tue, 2 Aug 2022 16:05:28 +0000 (16:05 +0000)
The quick settings menu is a popover that arranges items in a
reflowing, homogeneous grid. Grid children may span multiple
columns, but not rows.

For now the QuickSettingsMenu that contains the grid is just a
convenience wrapper around the layout manager that does the heavy
lifting. The two will become more intertwined when we add support
for menu toggles though, so the custom menu type is unfortunately
needed.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2392>

data/theme/gnome-shell-sass/_widgets.scss
data/theme/gnome-shell-sass/widgets/_quick-settings.scss [new file with mode: 0644]
data/theme/meson.build
js/js-resources.gresource.xml
js/ui/quickSettings.js [new file with mode: 0644]

index 4ed082f1f93c66eaab7d853c12915318a1b5cdfb..875baf5827ad3ceff7ca4a83071abe8692d11703 100644 (file)
@@ -33,6 +33,7 @@
 // Panel
 @import 'widgets/panel';
 @import 'widgets/corner-ripple';
+@import 'widgets/quick-settings';
 // Overview
 @import 'widgets/overview';
 @import 'widgets/window-picker';
diff --git a/data/theme/gnome-shell-sass/widgets/_quick-settings.scss b/data/theme/gnome-shell-sass/widgets/_quick-settings.scss
new file mode 100644 (file)
index 0000000..66a0ed3
--- /dev/null
@@ -0,0 +1,8 @@
+.quick-settings {
+  padding: 4 * $base_padding;
+}
+
+.quick-settings-grid {
+  spacing-rows: 3 * $base_padding;
+  spacing-columns: 2 * $base_padding;
+}
index 26471b6792e52521e5b886249b7da080d0995f37..fbf2938b88757d772a9a39f7cce2f677544adbf8 100644 (file)
@@ -29,6 +29,7 @@ theme_sources = files([
   'gnome-shell-sass/widgets/_overview.scss',
   'gnome-shell-sass/widgets/_panel.scss',
   'gnome-shell-sass/widgets/_popovers.scss',
+  'gnome-shell-sass/widgets/_quick-settings.scss',
   'gnome-shell-sass/widgets/_screen-shield.scss',
   'gnome-shell-sass/widgets/_screenshot.scss',
   'gnome-shell-sass/widgets/_scrollbars.scss',
index 642ad05c3d2e9bd373b9ff0c055f28a180210b0b..87adfd14032d622f491d7bc5bbaa4725a63b0e35 100644 (file)
@@ -93,6 +93,7 @@
     <file>ui/pointerA11yTimeout.js</file>
     <file>ui/pointerWatcher.js</file>
     <file>ui/popupMenu.js</file>
+    <file>ui/quickSettings.js</file>
     <file>ui/remoteSearch.js</file>
     <file>ui/ripples.js</file>
     <file>ui/runDialog.js</file>
diff --git a/js/ui/quickSettings.js b/js/ui/quickSettings.js
new file mode 100644 (file)
index 0000000..82a2358
--- /dev/null
@@ -0,0 +1,207 @@
+/* exported QuickSettingsMenu */
+const {Clutter, GLib, GObject, St} = imports.gi;
+
+const Main = imports.ui.main;
+const PopupMenu = imports.ui.popupMenu;
+
+const QuickSettingsLayoutMeta = GObject.registerClass({
+    Properties: {
+        'column-span': GObject.ParamSpec.int(
+            'column-span', '', '',
+            GObject.ParamFlags.READWRITE,
+            1, GLib.MAXINT32, 1),
+    },
+}, class QuickSettingsLayoutMeta extends Clutter.LayoutMeta {});
+
+const QuickSettingsLayout = GObject.registerClass({
+    Properties: {
+        'row-spacing': GObject.ParamSpec.int(
+            'row-spacing', 'row-spacing', 'row-spacing',
+            GObject.ParamFlags.READWRITE,
+            0, GLib.MAXINT32, 0),
+        'column-spacing': GObject.ParamSpec.int(
+            'column-spacing', 'column-spacing', 'column-spacing',
+            GObject.ParamFlags.READWRITE,
+            0, GLib.MAXINT32, 0),
+        'n-columns': GObject.ParamSpec.int(
+            'n-columns', 'n-columns', 'n-columns',
+            GObject.ParamFlags.READWRITE,
+            1, GLib.MAXINT32, 1),
+    },
+}, class QuickSettingsLayout extends Clutter.LayoutManager {
+    _containerStyleChanged() {
+        const node = this._container.get_theme_node();
+
+        let changed = false;
+        let found, length;
+        [found, length] = node.lookup_length('spacing-rows', false);
+        changed ||= found;
+        if (found)
+            this.rowSpacing = length;
+
+        [found, length] = node.lookup_length('spacing-columns', false);
+        changed ||= found;
+        if (found)
+            this.columnSpacing = length;
+
+        if (changed)
+            this.layout_changed();
+    }
+
+    _getColSpan(container, child) {
+        const {columnSpan} = this.get_child_meta(container, child);
+        return Math.clamp(columnSpan, 1, this.nColumns);
+    }
+
+    _getMaxChildWidth(container) {
+        let [minWidth, natWidth] = [0, 0];
+
+        for (const child of container) {
+            const [childMin, childNat] = child.get_preferred_width(-1);
+            const colSpan = this._getColSpan(container, child);
+            minWidth = Math.max(minWidth, childMin / colSpan);
+            natWidth = Math.max(natWidth, childNat / colSpan);
+        }
+
+        return [minWidth, natWidth];
+    }
+
+    _getRows(container) {
+        const rows = [];
+        let lineIndex = 0;
+        let curRow;
+
+        /** private */
+        function appendRow() {
+            curRow = [];
+            rows.push(curRow);
+            lineIndex = 0;
+        }
+
+        for (const child of container) {
+            if (!child.visible)
+                continue;
+
+            if (lineIndex === 0)
+                appendRow();
+
+            const colSpan = this._getColSpan(container, child);
+            const fitsRow = lineIndex + colSpan <= this.nColumns;
+
+            if (!fitsRow)
+                appendRow();
+
+            curRow.push(child);
+            lineIndex = (lineIndex + colSpan) % this.nColumns;
+        }
+
+        return rows;
+    }
+
+    _getRowHeight(children) {
+        let [minHeight, natHeight] = [0, 0];
+
+        children.forEach(child => {
+            const [childMin, childNat] = child.get_preferred_height(-1);
+            minHeight = Math.max(minHeight, childMin);
+            natHeight = Math.max(natHeight, childNat);
+        });
+
+        return [minHeight, natHeight];
+    }
+
+    vfunc_get_child_meta_type() {
+        return QuickSettingsLayoutMeta.$gtype;
+    }
+
+    vfunc_set_container(container) {
+        this._container?.disconnectObject(this);
+
+        this._container = container;
+
+        this._container?.connectObject('style-changed',
+            () => this._containerStyleChanged(), this);
+    }
+
+    vfunc_get_preferred_width(container, _forHeight) {
+        const [childMin, childNat] = this._getMaxChildWidth(container);
+        const spacing = (this.nColumns - 1) * this.column_spacing;
+        return [this.nColumns * childMin + spacing, this.nColumns * childNat + spacing];
+    }
+
+    vfunc_get_preferred_height(container, _forWidth) {
+        const rows = this._getRows(container);
+
+        let [minHeight, natHeight] = [0, 0];
+
+        const spacing = (rows.length - 1) * this.row_spacing;
+        minHeight += spacing;
+        natHeight += spacing;
+
+        rows.forEach(row => {
+            const [rowMin, rowNat] = this._getRowHeight(row);
+            minHeight += rowMin;
+            natHeight += rowNat;
+        });
+
+        return [minHeight, natHeight];
+    }
+
+    vfunc_allocate(container, box) {
+        const rows = this._getRows(container);
+
+        const availWidth = box.get_width() - (this.nColumns - 1) * this.row_spacing;
+        const childWidth = availWidth / this.nColumns;
+
+        const isRtl = container.text_direction === Clutter.TextDirection.RTL;
+
+        const childBox = new Clutter.ActorBox();
+        let y = box.y1;
+        rows.forEach(row => {
+            const [, rowNat] = this._getRowHeight(row);
+
+            let lineIndex = 0;
+            row.forEach(child => {
+                const colSpan = this._getColSpan(container, child);
+                const width =
+                    childWidth * colSpan + this.column_spacing * (colSpan - 1);
+                let x = box.x1 + lineIndex * (childWidth + this.column_spacing);
+                if (isRtl)
+                    x = box.x2 - width - x;
+
+                childBox.set_origin(x, y);
+                childBox.set_size(width, rowNat);
+                child.allocate(childBox);
+
+                lineIndex = (lineIndex + colSpan) % this.nColumns;
+            });
+
+            y += rowNat + this.row_spacing;
+        });
+    }
+});
+
+var QuickSettingsMenu = class extends PopupMenu.PopupMenu {
+    constructor(sourceActor, nColumns = 1) {
+        super(sourceActor, 0, St.Side.TOP);
+
+        Main.layoutManager.connectObject('system-modal-opened',
+            () => this.close(), this);
+
+        this.box.add_style_class_name('quick-settings');
+
+        this._grid = new St.Widget({
+            style_class: 'quick-settings-grid',
+            layout_manager: new QuickSettingsLayout({
+                nColumns,
+            }),
+        });
+        this.box.add_child(this._grid);
+    }
+
+    addItem(item, colSpan = 1) {
+        this._grid.add_child(item);
+        this._grid.layout_manager.child_set_property(
+            this._grid, item, 'column-span', colSpan);
+    }
+};